package spike.pool.impl;

import spike.pool.ConnectionPool;
import spike.pool.ConnectionSource;
import spike.pool.PoolFactory;

import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class MainTest {
    public static void main(String[] args) throws InterruptedException {
        PoolFactory<Connection> factory = new PoolFactoryImpl<>();
        final ConnectionPool<Connection> pool = factory.newConnectionPool(2, 7, 3, TimeUnit.SECONDS, new Source());

        pool.start();

        CountDownLatch latch = new CountDownLatch(1);

        getTestStart(pool, 20, latch);

        tryGetTestStart(pool, 20, latch);

        tryGetWithTimeoutTestStart(pool, 20, latch);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {}
        System.out.println("start");
        latch.countDown();


        for (int i = 0; i < 25; i++) {
            if(i == 10) {
                System.out.println("stop()");
                pool.stop();
            }
            Connection connection;
            while((connection = pool.get()) == null);
            connection.setUser("Linear user " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            connection.setUser("free");
            pool.release(connection);
        }

        pool.stop();
    }

    private static void tryGetWithTimeoutTestStart(final ConnectionPool<Connection> pool, int threadsNumber, final CountDownLatch latch) {
        for(int i = 0; i < threadsNumber; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    String threadName = Thread.currentThread().getName() + "{get(time)}";
                    Connection connection;
                    try {
                        while(true) {
                            connection = pool.get(15, TimeUnit.SECONDS);
                            if(connection == null) {
                            } else {
                                break;
                            }
                            Thread.sleep(1500);
                        }
                        connection.setUser(threadName);
                        Thread.sleep(500);
                        connection.setUser("free");
                        pool.release(connection);
                    } catch (InterruptedException e) {
                    }
                }
            }.start();
        }
    }

    private static void tryGetTestStart(final ConnectionPool<Connection> pool, int threadsNumber, final CountDownLatch latch) {
        for(int i = 0; i < threadsNumber; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    String threadName = Thread.currentThread().getName() + "{get()}";
                    Connection connection;
                    try {
                        while(true) {
                            connection = pool.tryGet();
                            if(connection != null) {
                                break;
                            }
                            Thread.sleep(1500);
                        }
                        connection.setUser(threadName);
                        Thread.sleep(1500);
                        connection.setUser("free");
                        pool.release(connection);
                    } catch (InterruptedException e) {
                    }
                }
            }.start();
        }
    }

    private static void getTestStart(final ConnectionPool<Connection> pool, int threadsNumber, final CountDownLatch latch) {
        for(int i = 0; i < threadsNumber; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    String threadName = Thread.currentThread().getName() + "{get()}";
                    try {
                        Connection connection = pool.get();
                        connection.setUser(threadName);
                        Thread.sleep(1000);
                        connection.setUser("free");
                        pool.release(connection);
                    } catch (InterruptedException e) {
                    }
                }
            }.start();
        }
    }
}

class Source implements ConnectionSource<Connection> {

    @Override
    public Connection open() {
        return new Connection();
    }

    @Override
    public void close(Connection object) {
        object.close();
    }
}

class Connection {
    private static final int COL_WIDTH = 25;
    private static final StringBuilder STRING_BUILDER = new StringBuilder(COL_WIDTH);

    private static Map<Integer, String> users = new Hashtable<>();

    private static AtomicInteger count = new AtomicInteger();
    private int n;

    public Connection() {
        n = count.incrementAndGet();
        users.put(n, "free");
        printHeader();
    }

    public void setUser(String user) {
        users.put(n, user);
        printUsers();
    }

    @Override
    public String toString() {
        return "Connection{" +
                "n=" + n +
                '}';
    }

    public void close() {
        users.remove(n);
        printHeader();
    }

    private void printHeader() {
        synchronized(users) {
            int lineLength = users.size() * (COL_WIDTH + 1) + 1;
            printLine(lineLength);
            for (Integer key : users.keySet()) {
                System.out.print("|" + expandString("Connection_" + key.toString()));
            }
            System.out.println("|");
            printLine(lineLength);
            printUsers();
        }
    }

    private void printUsers() {
        synchronized(users) {
            for (Integer key : users.keySet()) {
                System.out.print("|" + expandString(users.get(key)));
            }
            System.out.println("|");
        }
    }

    private void printLine(int n) {
        for (int i = 0; i < n; i++) {
            System.out.print("=");
        }
        System.out.println();
    }

    private String expandString(String source) {
        STRING_BUILDER.append(source);
        int steps = (COL_WIDTH - source.length()) / 2;
        for (int i = 0; i < steps; i++) {
            STRING_BUILDER.append(" ");
            STRING_BUILDER.insert(0, " ");
        }
        if(STRING_BUILDER.length() < COL_WIDTH) {
            STRING_BUILDER.append(" ");
        }
        String result = STRING_BUILDER.toString();
        STRING_BUILDER.delete(0, COL_WIDTH);
        return result;
    }
}
